package com.piece.core.message.client.impl;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.piece.core.framework.exception.ErrorCode;
import com.piece.core.framework.support.bootstrap.DataTable;
import com.piece.core.framework.support.convert.Convert;
import com.piece.core.framework.support.response.AjaxResponse;
import com.piece.core.framework.util.collection.ArrayUtil;
import com.piece.core.framework.util.collection.CollectionUtil;
import com.piece.core.framework.util.string.JsonUtil;
import com.piece.core.framework.util.string.StringUtil;
import com.piece.core.framework.util.time.DateUtil;
import com.piece.core.log.client.LogFactory;
import com.piece.core.message.client.dto.SmsReceiveRespDTO;
import com.piece.core.message.client.dto.SmsSendRespDTO;
import com.piece.core.message.client.dto.SmsTemplateRespDTO;
import com.piece.core.message.constant.SmsErrorCodeConstants;
import com.piece.core.message.enums.AuditStatusType;
import com.piece.core.message.properties.SmsProperties;
import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.sms.v20210111.SmsClient;
import com.tencentcloudapi.sms.v20210111.models.*;
import lombok.Builder;
import lombok.Data;
import org.apache.commons.collections.map.LinkedMap;
import org.springframework.util.Assert;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * 腾讯云短信功能实现
 */
public class TencentSmsClient extends AbstractSmsClient {

    /**
     * 调用成功 code
     */
    public static final String API_SUCCESS_CODE = "Ok";

    /**
     * REGION，使用南京
     */
    private static final String ENDPOINT = "ap-nanjing";

    /**
     * 是否国际/港澳台短信：
     * 0：表示国内短信。
     * 1：表示国际/港澳台短信。
     */
    private static final long INTERNATIONAL = 0L;

    private SmsClient client;

    public TencentSmsClient(String applicationName, SmsProperties properties, LogFactory auditLogFactory) {
        super(applicationName, properties, auditLogFactory);
        Assert.notEmpty(new Object[]{ properties.getApiSecret() }, "apiSecret 不能为空");
    }

    @Override
    protected void doInit() {
        Credential credential = new Credential(properties.getApiKey(), properties.getApiSecret());
        client = new SmsClient(credential, ENDPOINT);
    }

    @Override
    protected AjaxResponse doSendSms(String logId, String mobile, String apiTemplateId, LinkedMap templateParams) {
        return invoke(() -> buildSendSmsRequest(logId, mobile, apiTemplateId, templateParams),
                this::doSendSmsSDK,
                response -> {
                    SendStatus sendStatus = response.getSendStatusSet()[0];
                    return AjaxResponse.result(response.getRequestId(), convert(sendStatus.getCode(), sendStatus.getMessage()),
                            SmsSendRespDTO.builder()
                                    .bizId(sendStatus.getSerialNo())
                                    .outId(logId)
                                    .content(sendStatus.getSessionContext())
                                    .receiveDate(DateUtil.formatDate(new Date(), DateUtil.FORMAT_YYYY_MM_DD_HH_MM_SS))
                                    .build());
                });
    }

    /**
     * 调用腾讯云 SDK 发送短信
     *
     * @param request 发送短信请求
     * @return 发送短信响应
     */
    private SendSmsResponse doSendSmsSDK(SendSmsRequest request) throws TencentCloudSDKException {
        return client.SendSms(request);
    }

    /**
     * 封装腾讯云发送短信请求
     *
     * @param logId          日志编号
     * @param mobile         手机号
     * @param apiTemplateId  模板编号
     * @param templateParams 短信模板参数
     * @return 腾讯云发送短信请求
     */
    private SendSmsRequest buildSendSmsRequest(String logId, String mobile, String apiTemplateId, LinkedMap templateParams) {
        SendSmsRequest request = new SendSmsRequest();
        request.setSmsSdkAppId(properties.getAppId());
        request.setPhoneNumberSet(new String[]{mobile});
        request.setSignName(properties.getSignature());
        request.setTemplateId(apiTemplateId);
        request.setTemplateParamSet(ArrayUtil.convertArray(templateParams, String.class, o -> {
            return Convert.toStr(((LinkedMap) o).getValue(0));
        }));
        request.setSessionContext(JsonUtil.toJson(SessionContext.builder().logId(logId).build()));
        return request;
    }

    @Override
    protected List<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) {
        List<SmsReceiveStatus> callback = JsonUtil.toList(text, SmsReceiveStatus.class);
        return CollectionUtil.convertList(callback, status -> {
            SmsReceiveRespDTO data = SmsReceiveRespDTO.builder()
                    .errorCode(status.getErrCode())
                    .errorMsg(status.getDescription())
                    .receiveTime(status.getReceiveTime())
                    .success(SmsReceiveStatus.SUCCESS_CODE.equalsIgnoreCase(status.getStatus()))
                    .mobile(status.getMobile())
                    .bizId(status.getSerialNo())
                    .build();

            String logId;
            SessionContext context;
            Assert.notNull(context = status.getSessionContext(), "回执信息中未解析出 context，请联系腾讯云小助手");
            Assert.notNull(logId = context.getLogId(), "回执信息中未解析出 logId，请联系腾讯云小助手");
            data.setOutId(Convert.toStr(logId));
            return data;
        });
    }

    @Override
    protected AjaxResponse doGetSmsTemplate(String apiTemplateId) {
        return invoke(() -> this.buildSmsTemplateStatusRequest(apiTemplateId),
                this::doGetSmsTemplateSDK,
                response -> AjaxResponse.result(response.getRequestId(), ErrorCode.SUCCESS,
                        convertTemplateStatusDTO(response.getDescribeTemplateStatusSet()[0]))
                );
    }

    private SmsTemplateRespDTO convertTemplateStatusDTO(DescribeTemplateListStatus templateStatus) {
        if (null == templateStatus) {
            return null;
        }

        AuditStatusType auditStatus;
        switch (templateStatus.getStatusCode().intValue()) {
            case -1:
                auditStatus = AuditStatusType.FAIL;
                break;
            case 0:
                auditStatus = AuditStatusType.SUCCESS;
                break;
            case 1:
                auditStatus = AuditStatusType.CHECKING;
                break;
            default:
                throw new IllegalStateException(StringUtil.format("不能解析短信模版审核状态{}，模版 id{}",
                        templateStatus.getStatusCode(), templateStatus.getTemplateId()));
        }

        return SmsTemplateRespDTO.builder()
                .id(String.valueOf(templateStatus.getTemplateId()))
                .content(templateStatus.getTemplateContent())
                .auditStatus(auditStatus.getStatus())
                .auditReason(templateStatus.getReviewReply())
                .build();
    }

    /**
     * 封装查询模版审核状态请求
     */
    private DescribeSmsTemplateListRequest buildSmsTemplateStatusRequest(String templateId) {
        DescribeSmsTemplateListRequest request = new DescribeSmsTemplateListRequest();
        request.setTemplateIdSet(new Long[]{Long.parseLong(templateId)});
        // 地区 0：表示国内短信。1：表示国际/港澳台短信。
        request.setInternational(INTERNATIONAL);
        return request;
    }

    /**
     * 调用腾讯云 SDK 查询短信模版状态
     */
    private DescribeSmsTemplateListResponse doGetSmsTemplateSDK(DescribeSmsTemplateListRequest request) throws TencentCloudSDKException {
        return client.DescribeSmsTemplateList(request);
    }

    @Override
    public AjaxResponse queryDetail(String mobile, String bizId, int pageIndex, int pageSize) {
        DataTable dataTable = new DataTable();
        dataTable.setStart(pageIndex);
        dataTable.setLength(pageSize);
        dataTable.setRecordsTotal(0);
        dataTable.setData(Collections.EMPTY_LIST);
        return AjaxResponse.result(ErrorCode.SUCCESS, dataTable);
    }

    private <Q, P> AjaxResponse invoke(Supplier<Q> requestSupplier, SdkFunction<Q, P> responseSupplier, Function<P, AjaxResponse> resultGen) {
        // 构建请求body
        Q request = requestSupplier.get();
        P response;
        try {
            // 调用腾讯云发送短信
            response = responseSupplier.apply(request);
        } catch (TencentCloudSDKException e) {
            return AjaxResponse.result(e.getRequestId(), convert(e.getErrorCode(), e.getMessage()), null);
        }
        return resultGen.apply(response);
    }

    private ErrorCode convert(String code, String msg) {
        switch (code) {
            case TencentSmsClient.API_SUCCESS_CODE: return ErrorCode.SUCCESS;
            case "FailedOperation.ContainSensitiveWord": return SmsErrorCodeConstants.SMS_SEND_CONTENT_INVALID;
            case "FailedOperation.JsonParseFail":
            case "MissingParameter.EmptyPhoneNumberSet":
            case "LimitExceeded.PhoneNumberCountLimit":
            case "FailedOperation.FailResolvePacket": return ErrorCode.ARGUMENT;
            case "FailedOperation.InsufficientBalanceInSmsPackage": return SmsErrorCodeConstants.SMS_ACCOUNT_MONEY_NOT_ENOUGH;
            case "FailedOperation.MarketingSendTimeConstraint": return SmsErrorCodeConstants.SMS_SEND_MARKET_LIMIT_CONTROL;
            case "FailedOperation.PhoneNumberInBlacklist": return SmsErrorCodeConstants.SMS_MOBILE_BLACK;
            case "FailedOperation.SignatureIncorrectOrUnapproved": return SmsErrorCodeConstants.SMS_SIGN_INVALID;
            case "FailedOperation.MissingTemplateToModify":
            case "FailedOperation.TemplateIncorrectOrUnapproved": return SmsErrorCodeConstants.SMS_TEMPLATE_INVALID;
            case "InvalidParameterValue.IncorrectPhoneNumber": return SmsErrorCodeConstants.SMS_MOBILE_INVALID;
            case "InvalidParameterValue.SdkAppIdNotExist": return SmsErrorCodeConstants.SMS_APP_ID_INVALID;
            case "InvalidParameterValue.TemplateParameterLengthLimit":
            case "InvalidParameterValue.TemplateParameterFormatError": return SmsErrorCodeConstants.SMS_TEMPLATE_PARAM_ERROR;
            case "LimitExceeded.PhoneNumberDailyLimit": return SmsErrorCodeConstants.SMS_SEND_DAY_LIMIT_CONTROL;
            case "LimitExceeded.PhoneNumberThirtySecondLimit":
            case "LimitExceeded.PhoneNumberOneHourLimit": return SmsErrorCodeConstants.SMS_SEND_BUSINESS_LIMIT_CONTROL;
            case "UnauthorizedOperation.RequestPermissionDeny":
            case "FailedOperation.ForbidAddMarketingTemplates":
            case "FailedOperation.NotEnterpriseCertification":
            case "UnauthorizedOperation.IndividualUserMarketingSmsPermissionDeny": return SmsErrorCodeConstants.SMS_PERMISSION_DENY;
            case "UnauthorizedOperation.RequestIpNotInWhitelist": return SmsErrorCodeConstants.SMS_IP_DENY;
            case "AuthFailure.SecretIdNotFound": return SmsErrorCodeConstants.SMS_ACCOUNT_INVALID;
        }
        return new ErrorCode(500, msg);
    }

    @Data
    private static class SmsReceiveStatus {
        /**
         * 短信接受成功 code
         */
        public static final String SUCCESS_CODE = "SUCCESS";
        /**
         * 用户实际接收到短信的时间
         */
        @JsonProperty("user_receive_time")
        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
        private Date receiveTime;
        /**
         * 国家（或地区）码
         */
        @JsonProperty("nationcode")
        private String nationCode;
        /**
         * 手机号码
         */
        private String mobile;
        /**
         * 实际是否收到短信接收状态，SUCCESS（成功）、FAIL（失败）
         */
        @JsonProperty("report_status")
        private String status;
        /**
         * 用户接收短信状态码错误信息
         */
        @JsonProperty("errmsg")
        private String errCode;
        /**
         * 用户接收短信状态描述
         */
        @JsonProperty("description")
        private String description;
        /**
         * 本次发送标识 ID（与发送接口返回的SerialNo对应）
         */
        @JsonProperty("sid")
        private String serialNo;
        /**
         * 用户的 session 内容（与发送接口的请求参数SessionContext一致）
         */
        @JsonProperty("ext")
        private SessionContext sessionContext;
    }

    @Data
    @Builder
    static class SessionContext {
        /**
         * 发送短信记录id
         */
        private String logId;
    }

    private interface SdkFunction<T, R> {
        R apply(T t) throws TencentCloudSDKException;
    }
}